<template>
  <div class="markdown-message" :class="{ 'message-sent': isSent }">
    <!-- 图片预览 -->
    <div v-if="actualFileUrl && isImage" class="message-image">
      <!-- 加载中或加载失败 -->
      <div v-if="!imageUrl && !imageError" class="image-loading">
        <q-spinner size="sm" />
        <span>加载中...</span>
      </div>
      <!-- 加载失败 -->
      <div v-else-if="imageError" class="image-error">
        <q-icon name="broken_image" size="md" />
        <span>图片加载失败</span>
      </div>
      <!-- 图片 -->
      <img v-else-if="imageUrl" :src="imageUrl" :alt="fileName || '图片'" @error="handleImageError" class="image-preview" />
    </div>
    <!-- 文件链接（非图片文件） -->
    <div v-else-if="(actualFileUrl || messageContentType === 3) && !isImage && (fileName || messageContentType === 3)" class="message-file">
      <div class="file-icon-wrapper">
        <q-icon :name="getFileIcon()" size="md" />
      </div>
      <div class="file-info">
        <div class="file-name-wrapper">
          <a 
            v-if="actualFileUrl" 
            :href="actualFileUrl" 
            class="file-name" 
            :download="fileName" 
            @click="handleFileDownload"
          >
            {{ getDisplayFileName() }}
          </a>
          <span v-else class="file-name file-name-disabled">
            {{ getDisplayFileName() }}
          </span>
        </div>
        <div v-if="getFileSize()" class="file-size">
          {{ getFileSize() }}
        </div>
      </div>
    </div>
    <div v-if="shouldShowContent" class="markdown-content" ref="markdownRef"></div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick, onUnmounted } from 'vue';
import MarkdownIt from 'markdown-it';
import { API_BASE_URL } from '@/config/env';
import { getCachedImageUrl } from '@/services/image-cache';
import { getUser, getUserAsync } from '@/services/auth';
import { isTauri, storageGet } from '@/services/secure-storage';

interface Props {
  content: string;
  isSent?: boolean;
  fileUrl?: string;
  fileName?: string;
  fileType?: string;
  messageContentType?: number; // 消息内容类型：1=文本, 2=图片, 3=文件
}

const props = withDefaults(defineProps<Props>(), {
  isSent: false,
  fileUrl: undefined,
  fileName: undefined,
  fileType: undefined,
  messageContentType: undefined,
});

const markdownRef = ref<HTMLElement | null>(null);
const imageError = ref(false);
const currentUserName = ref<string | null>(null);

// 获取当前用户信息
const loadCurrentUser = async () => {
  try {
    if (isTauri()) {
      const user = await getUserAsync();
      currentUserName.value = user?.name || null;
    } else {
      const user = getUser();
      currentUserName.value = user?.name || null;
    }
  } catch (e) {
    console.warn('获取当前用户信息失败:', e);
    // 尝试从 localStorage 获取
    try {
      const userStr = localStorage.getItem('user');
      if (userStr) {
        const user = JSON.parse(userStr);
        currentUserName.value = user?.name || null;
      }
    } catch (e2) {
      console.warn('从 localStorage 获取用户信息失败:', e2);
    }
  }
};

// 初始化时加载用户信息
onMounted(() => {
  loadCurrentUser();
  
  // 监听用户信息变化（例如登录、切换用户等）
  const handleStorageChange = () => {
    loadCurrentUser();
  };
  
  // 监听 localStorage 变化（浏览器环境）
  if (!isTauri()) {
    window.addEventListener('storage', handleStorageChange);
  }
  
  // 定期检查用户信息（Tauri 环境）
  const checkInterval = setInterval(() => {
    loadCurrentUser();
  }, 5000); // 每5秒检查一次
  
  onUnmounted(() => {
    if (!isTauri()) {
      window.removeEventListener('storage', handleStorageChange);
    }
    clearInterval(checkInterval);
  });
});

// 从 content 中提取文件/图片 URL（如果没有 fileUrl）
const extractedFileUrl = computed(() => {
  // 如果已经有 fileUrl，直接返回
  if (props.fileUrl) {
    return props.fileUrl;
  }
  
  // 如果没有 content，返回空
  if (!props.content || typeof props.content !== 'string') {
    return '';
  }
  
  const content = props.content.trim();
  
  // 检查是否是 base64 data URI 图片
  if (content.startsWith('data:image/')) {
    return content;
  }
  
  // 检查是否是完整的 HTTP/HTTPS URL（图片或文件）
  if (content.startsWith('http://') || content.startsWith('https://')) {
    return content;
  }
  
  // 检查是否是相对路径的 URL（/api/upload/）
  if (content.startsWith('/api/upload/')) {
    return `${API_BASE_URL}${content}`;
  }
  
  // 检查是否是完整的 HTTP/HTTPS 图片 URL（带扩展名）
  const httpImagePattern = new RegExp('https?:\\/\\/[^\\s]+\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?[^\\s]*)?', 'i');
  const httpMatch = content.match(httpImagePattern);
  if (httpMatch) {
    return httpMatch[0];
  }
  
  // 检查是否是相对路径的图片 URL（/api/upload/）
  const apiUploadPattern = new RegExp('^\\/api\\/upload\\/[^\\s]+\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?[^\\s]*)?', 'i');
  if (content.match(apiUploadPattern)) {
    return `${API_BASE_URL}${content}`;
  }
  
  // 检查 content 是否只包含一个图片 URL（没有其他文本）
  const urlOnlyPattern = new RegExp('^(https?:\\/\\/[^\\s]+\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?[^\\s]*)?|\\/api\\/upload\\/[^\\s]+\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?[^\\s]*)?)$', 'i');
  if (urlOnlyPattern.test(content)) {
    if (content.startsWith('/api/upload/')) {
      return `${API_BASE_URL}${content}`;
    }
    return content;
  }
  
  return '';
});

// 实际使用的 fileUrl（优先使用 props.fileUrl，否则使用从 content 中提取的）
const actualFileUrl = computed(() => {
  // 如果是文件消息（message_content_type === 3），优先从 content 中提取 URL
  if (props.messageContentType === 3 && props.content) {
    const content = String(props.content).trim();
    if (content.startsWith('http://') || content.startsWith('https://') || content.startsWith('/api/upload/')) {
      if (content.startsWith('/api/upload/')) {
        return `${API_BASE_URL}${content}`;
      }
      return content;
    }
  }
  return props.fileUrl || extractedFileUrl.value;
});

// 判断是否应该显示内容
const shouldShowContent = computed(() => {
  if (!props.content) return false;
  
  // 如果是文件消息（message_content_type === 3），不显示文本内容，只显示文件名
  if (props.messageContentType === 3) {
    return false;
  }
  
  // 如果从 content 中提取到了文件/图片 URL，且 content 只包含这个 URL，则不显示文本内容
  if (extractedFileUrl.value && !props.fileUrl) {
    const trimmedContent = props.content.trim();
    // 如果 content 就是文件/图片 URL（可能带换行符），则不显示文本
    const urlProtocolPattern = new RegExp('^https?:\\/\\/[^\\/]+', '');
    const whitespacePattern = new RegExp('\\s+', 'g');
    if (trimmedContent === extractedFileUrl.value || 
        trimmedContent === extractedFileUrl.value.replace(urlProtocolPattern, '') ||
        trimmedContent.replace(whitespacePattern, '') === extractedFileUrl.value.replace(whitespacePattern, '')) {
      return false;
    }
  }
  
  // 如果是文件消息，且 content 是 URL，则不显示文本内容
  if (props.messageContentType === 3 && props.content) {
    const content = String(props.content).trim();
    if (content.startsWith('http://') || content.startsWith('https://') || content.startsWith('/api/upload/')) {
      return false;
    }
  }
  
  // 如果是图片消息，且content只包含文件名，则不显示
  if (actualFileUrl.value && isImage.value && props.fileName) {
    const trimmedContent = props.content.trim();
    // 如果content就是文件名，或者只包含文件名，则不显示
    const fileExtPattern = new RegExp('\\.(jpg|jpeg|png|gif|webp|bmp|svg)$', 'i');
    if (trimmedContent === props.fileName || trimmedContent === props.fileName.replace(fileExtPattern, '')) {
      return false;
    }
  }
  
  return true;
});

// 缓存的图片 URL
const cachedImageUrl = ref<string>('');

// 获取完整的图片 URL（使用缓存）
const imageUrl = computed(() => {
  if (!actualFileUrl.value) return '';
  
  // 如果是 base64 data URI，直接返回
  if (actualFileUrl.value.startsWith('data:image/')) {
    return actualFileUrl.value;
  }
  
  // 如果已经有缓存的 URL，使用缓存
  if (cachedImageUrl.value) {
    return cachedImageUrl.value;
  }
  
  // 对于需要认证的 API 端点，在缓存完成前不返回 URL
  // 这样可以避免浏览器直接请求时缺少 Authorization header
  if (actualFileUrl.value.includes('/api/upload/') || actualFileUrl.value.includes('api/upload/')) {
    // 如果正在加载缓存，返回空字符串（不显示图片，等待缓存完成）
    // 如果缓存失败，loadCachedImage 会设置 cachedImageUrl 为空并设置 imageError
    return '';
  }
  
  // 对于其他 URL（如外部图片），直接返回
  return actualFileUrl.value;
});

// 判断是否为图片
const isImage = computed(() => {
  if (imageError.value) return false;
  
  // 优先使用 message_content_type 判断（最准确）
  if (props.messageContentType === 2) {
    return true;
  }
  
  if (props.fileType) {
    return props.fileType.startsWith('image/');
  }
  
  const urlToCheck = actualFileUrl.value;
  if (urlToCheck) {
    const url = urlToCheck.toLowerCase();
    // 检查是否是 base64 data URI 图片
    if (url.startsWith('data:image/')) {
      return true;
    }
    // 检查是否是普通图片 URL（包括 /api/upload/ 路径）
    if (url.includes('/api/upload/') || url.includes('/upload/')) {
      return true;
    }
    // 检查是否是 HTTP/HTTPS 图片 URL（即使没有扩展名，如果 message_content_type 是 2，也认为是图片）
    if (url.startsWith('http://') || url.startsWith('https://')) {
      // 如果有扩展名，直接判断
      const imageExtPattern = new RegExp('\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?|$)', 'i');
      if (url.match(imageExtPattern)) {
        return true;
      }
      // 如果没有扩展名，但 message_content_type 是 2，也认为是图片
      if (props.messageContentType === 2) {
        return true;
      }
    }
    // 检查是否是普通图片 URL（有扩展名）
    const imageExtPattern2 = new RegExp('\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?|$)', 'i');
    return url.match(imageExtPattern2) !== null;
  }
  
  if (props.fileName) {
    const name = props.fileName.toLowerCase();
    const imageExtPattern3 = new RegExp('\\.(jpg|jpeg|png|gif|webp|bmp|svg)$', 'i');
    return name.match(imageExtPattern3) !== null;
  }
  
  return false;
});

// 获取文件图标
const getFileIcon = (): string => {
  if (!props.fileName && !props.fileType) return 'attach_file';
  
  const name = (props.fileName || '').toLowerCase();
  const type = (props.fileType || '').toLowerCase();
  
  if (type.includes('pdf') || name.endsWith('.pdf')) return 'picture_as_pdf';
  const docPattern = new RegExp('\\.(doc|docx)$');
  const excelPattern = new RegExp('\\.(xls|xlsx)$');
  const zipPattern = new RegExp('\\.(zip|rar|7z)$');
  const audioPattern = new RegExp('\\.(mp3|wav|flac|aac)$');
  const videoPattern = new RegExp('\\.(mp4|avi|mov|wmv)$');
  if (type.includes('word') || name.match(docPattern)) return 'description';
  if (type.includes('excel') || name.match(excelPattern)) return 'table_chart';
  if (type.includes('zip') || name.match(zipPattern)) return 'folder_zip';
  if (type.includes('audio') || name.match(audioPattern)) return 'audiotrack';
  if (type.includes('video') || name.match(videoPattern)) return 'videocam';
  
  return 'attach_file';
};

// 获取显示的文件名（如果是 UUID 格式，尝试从 extra 中获取原始文件名）
const getDisplayFileName = (): string => {
  // 如果有文件名，直接使用
  if (props.fileName) {
    // 如果是 UUID 格式的文件名（包含连字符），尝试从 content 或其他地方获取原始文件名
    const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\./i;
    if (uuidPattern.test(props.fileName)) {
      // 尝试从 content 中提取原始文件名（如果有的话）
      // 或者直接显示扩展名
      const ext = props.fileName.split('.').pop() || '';
      return `文件.${ext}`;
    }
    return props.fileName;
  }
  
  // 如果没有文件名，但 content 是 URL，尝试从 URL 中提取文件名
  if (props.content && (props.content.startsWith('http://') || props.content.startsWith('https://') || props.content.startsWith('/api/upload/'))) {
    try {
      const url = props.content.startsWith('/api/upload/') ? `${API_BASE_URL}${props.content}` : props.content;
      const urlObj = new URL(url);
      const pathname = urlObj.pathname;
      const fileName = pathname.split('/').pop() || '';
      if (fileName) {
        // 如果是 UUID 格式，只显示扩展名
        const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\./i;
        if (uuidPattern.test(fileName)) {
          const ext = fileName.split('.').pop() || '';
          return `文件.${ext}`;
        }
        return fileName;
      }
    } catch (e) {
      // URL 解析失败，忽略
    }
  }
  
  return '文件';
};

// 获取文件大小（如果有的话）
const getFileSize = (): string => {
  // 目前没有文件大小信息，返回空字符串
  // 如果将来需要显示文件大小，可以从 extra 或其他地方获取
  return '';
};

// 处理文件下载
const handleFileDownload = async (event: MouseEvent) => {
  if (!actualFileUrl.value) return;
  
  // 如果文件需要认证，需要先获取文件内容再下载
  if (actualFileUrl.value.includes('/api/upload/')) {
    event.preventDefault();
    try {
      const token = localStorage.getItem('token');
      const response = await fetch(actualFileUrl.value, {
        headers: {
          'Authorization': `Bearer ${token}`,
        },
      });
      
      if (!response.ok) {
        throw new Error('文件下载失败');
      }
      
      const blob = await response.blob();
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = getDisplayFileName();
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);
    } catch (error) {
      console.error('文件下载失败:', error);
    }
  }
  // 对于其他 URL，浏览器会自动处理下载（因为有 download 属性）
};

// 处理图片加载错误
const handleImageError = (event?: Event) => {
  console.error('图片加载错误:', {
    imageUrl: imageUrl.value,
    actualFileUrl: actualFileUrl.value,
    cachedImageUrl: cachedImageUrl.value,
    event
  });
  imageError.value = true;
  // 如果缓存失败，尝试重新加载
  if (actualFileUrl.value && !cachedImageUrl.value) {
    loadCachedImage();
  }
};

// 创建 markdown-it 实例 - 使用最简单的配置
const md = new MarkdownIt({
  html: false, // 禁用HTML标签，避免安全问题
  breaks: true, // 将换行符转换为 <br>
  linkify: true, // 自动识别链接
  typographer: false, // 禁用排版优化，避免乱码
});

// 处理@提及（在Markdown渲染之前）
const processMentions = (text: string): string => {
  // 匹配@用户名格式（@后面跟非空格字符，直到遇到空格、换行或文本末尾）
  // 使用非贪婪匹配，避免匹配到多个@
  const mentionPattern = new RegExp('@([^\\s@\\n]+)', 'g');
  const ampPattern = new RegExp('&', 'g');
  const ltPattern = new RegExp('<', 'g');
  const gtPattern = new RegExp('>', 'g');
  const quotPattern = new RegExp('"', 'g');
  const aposPattern = new RegExp("'", 'g');
  
  return text.replace(mentionPattern, (match, username) => {
    // 转义HTML特殊字符，避免XSS攻击
    const escapedUsername = username
      .replace(ampPattern, '&amp;')
      .replace(ltPattern, '&lt;')
      .replace(gtPattern, '&gt;')
      .replace(quotPattern, '&quot;')
      .replace(aposPattern, '&#39;');
    
    // 检查是否@的是当前用户（不区分大小写）
    const isMentionedSelf = currentUserName.value && 
                           username.toLowerCase() === currentUserName.value.toLowerCase();
    
    // 如果@的是当前用户，添加 mention-self class 来高亮显示
    const mentionClass = isMentionedSelf ? 'mention mention-self' : 'mention';
    
    // 返回高亮的@提及
    return '<span class="' + mentionClass + '">@' + escapedUsername + '</span>';
  });
};

// 渲染Markdown
const renderMarkdown = (text: string): string => {
  if (!text || typeof text !== 'string') {
    return '';
  }
  
  let trimmedText = text.trim();
  if (!trimmedText) {
    return '';
  }
  
  // 如果是图片消息，过滤掉文件名（UUID格式的文件名）
  if (actualFileUrl.value && isImage.value && props.fileName) {
    // 移除文件名（可能是UUID格式，包含连字符）
    const fileNamePattern = new RegExp(props.fileName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
    trimmedText = trimmedText.replace(fileNamePattern, '').trim();
    // 如果过滤后内容为空，则不显示
    if (!trimmedText) {
      return '';
    }
  }
  
  // 如果从 content 中提取到了图片 URL，且 content 只包含这个 URL，则从文本中移除
  if (extractedFileUrl.value && !props.fileUrl) {
    const escapePattern2 = new RegExp('[.*+?^${}()|[\\]\\\\]', 'g');
    const imageUrlPattern = new RegExp(extractedFileUrl.value.replace(escapePattern2, '\\$&'), 'gi');
    trimmedText = trimmedText.replace(imageUrlPattern, '').trim();
    // 如果过滤后内容为空，则不显示
    if (!trimmedText) {
      return '';
    }
  }
  
  try {
    // 先处理@提及
    trimmedText = processMentions(trimmedText);
    
    // 使用 markdown-it 渲染（注意：markdown-it会转义HTML，所以我们需要在渲染后再次处理）
    // 但markdown-it默认会转义HTML标签，所以我们需要先渲染，然后再处理@
    // 更好的方法是使用markdown-it的插件，但为了简单，我们在渲染后处理
    let html = md.render(trimmedText);
    
    // 由于markdown-it会转义HTML，我们需要在渲染后重新处理@提及
    // 但这样会有问题，因为markdown-it已经转义了HTML
    // 更好的方法是在markdown渲染之前处理@，但需要确保不会破坏markdown语法
    // 我们使用一个技巧：先用特殊标记替换@提及，渲染后再替换回来
    const mentionPlaceholders: string[] = [];
    const placeholderPattern = new RegExp('MENTION_PLACEHOLDER_(\\d+)', 'g');
    
    // 先用占位符替换@提及
    trimmedText = text.trim();
    const mentionReplacePattern = new RegExp('@([^\\s@\\n]+)', 'g');
    trimmedText = trimmedText.replace(mentionReplacePattern, (match) => {
      const index = mentionPlaceholders.length;
      mentionPlaceholders.push(match);
      return `MENTION_PLACEHOLDER_${index}`;
    });
    
    // 渲染markdown
    html = md.render(trimmedText);
    
    // 将占位符替换为高亮的@提及
    html = html.replace(placeholderPattern, (match, index) => {
      const mention = mentionPlaceholders[parseInt(index)];
      if (mention) {
        const username = mention.substring(1); // 去掉@符号
        const ampPattern2 = new RegExp('&', 'g');
        const ltPattern2 = new RegExp('<', 'g');
        const gtPattern2 = new RegExp('>', 'g');
        const quotPattern2 = new RegExp('"', 'g');
        const aposPattern2 = new RegExp("'", 'g');
        const escapedUsername = username
          .replace(ampPattern2, '&amp;')
          .replace(ltPattern2, '&lt;')
          .replace(gtPattern2, '&gt;')
          .replace(quotPattern2, '&quot;')
          .replace(aposPattern2, '&#39;');
        
        // 检查是否@的是当前用户（不区分大小写）
        const isMentionedSelf = currentUserName.value && 
                               username.toLowerCase() === currentUserName.value.toLowerCase();
        
        // 如果@的是当前用户，添加 mention-self class 来高亮显示
        const mentionClass = isMentionedSelf ? 'mention mention-self' : 'mention';
        
        return '<span class="' + mentionClass + '">@' + escapedUsername + '</span>';
      }
      return match;
    });
    
    return html;
  } catch (e) {
    console.error('Markdown渲染错误:', e);
    // 如果渲染失败，返回转义的纯文本，但保留@高亮
    const ampPattern3 = new RegExp('&', 'g');
    const ltPattern3 = new RegExp('<', 'g');
    const gtPattern3 = new RegExp('>', 'g');
    const quotPattern3 = new RegExp('"', 'g');
    const aposPattern3 = new RegExp("'", 'g');
    const newlinePattern = new RegExp('\\n', 'g');
    let escaped = trimmedText
      .replace(ampPattern3, '&amp;')
      .replace(ltPattern3, '&lt;')
      .replace(gtPattern3, '&gt;')
      .replace(quotPattern3, '&quot;')
      .replace(aposPattern3, '&#39;')
      .replace(newlinePattern, '<br>');
    
    // 处理@提及
    escaped = processMentions(escaped);
    
    return escaped;
  }
};

// 更新内容
const updateContent = () => {
  if (markdownRef.value) {
    if (props.content && shouldShowContent.value) {
      const html = renderMarkdown(props.content);
      markdownRef.value.innerHTML = html;
    } else {
      markdownRef.value.innerHTML = '';
    }
  }
};

// 加载并缓存图片
const loadCachedImage = async () => {
  if (!actualFileUrl.value || !isImage.value) {
    cachedImageUrl.value = '';
    return;
  }
  
  // base64 data URI 不需要缓存
  if (actualFileUrl.value.startsWith('data:image/')) {
    cachedImageUrl.value = actualFileUrl.value;
    return;
  }
  
  // 添加超时处理，避免无限等待
  const timeoutPromise = new Promise<never>((_, reject) => {
    setTimeout(() => {
      reject(new Error('图片加载超时'));
    }, 30000); // 30秒超时
  });
  
  try {
    // 构建完整 URL（actualFileUrl 可能已经是完整 URL，也可能只是相对路径）
    let fullUrl = actualFileUrl.value;
    if (fullUrl.startsWith('/api/upload/')) {
      fullUrl = `${API_BASE_URL}${fullUrl}`;
    }
    
    // 获取缓存的图片 URL（这会自动下载并缓存，返回 blob URL 或本地文件 URL）
    // 使用 Promise.race 添加超时处理
    const cachedUrl = await Promise.race([
      getCachedImageUrl(fullUrl),
      timeoutPromise
    ]);
    
    // 如果返回空字符串，说明加载失败
    if (!cachedUrl || cachedUrl === '') {
      console.warn('⚠️ 图片缓存返回空字符串，可能加载失败');
      cachedImageUrl.value = '';
      imageError.value = true;
      return;
    }
    
    cachedImageUrl.value = cachedUrl;
  } catch (error: any) {
    console.error('❌ 加载图片缓存失败:', error);
    
    // 如果是超时错误，直接标记为失败
    if (error?.message === '图片加载超时') {
      console.error('❌ 图片加载超时');
      cachedImageUrl.value = '';
      imageError.value = true;
      return;
    }
    // 对于需要认证的 API 端点，缓存失败时尝试重试一次
    if (actualFileUrl.value.includes('/api/upload/')) {
      console.warn('⚠️ API 端点图片缓存失败，尝试重试...');
      // 重试一次
      try {
        let fullUrl = actualFileUrl.value;
        if (fullUrl.startsWith('/api/upload/')) {
          fullUrl = `${API_BASE_URL}${fullUrl}`;
        }
        // 重试时也添加超时处理
        const cachedUrl = await Promise.race([
          getCachedImageUrl(fullUrl),
          timeoutPromise
        ]);
        
        // 如果重试后仍然返回空字符串，标记为错误
        if (!cachedUrl || cachedUrl === '') {
          console.error('❌ 重试后仍然返回空字符串');
          cachedImageUrl.value = '';
          imageError.value = true;
          return;
        }
        
        cachedImageUrl.value = cachedUrl;
      } catch (retryError: any) {
        console.error('❌ 重试也失败:', retryError);
        // 保持为空，不显示图片
        cachedImageUrl.value = '';
        imageError.value = true;
      }
    } else {
      // 对于外部图片，可以直接使用原始 URL
      // 但如果原始 URL 也无效，应该标记为错误
      if (actualFileUrl.value) {
        cachedImageUrl.value = actualFileUrl.value;
      } else {
        cachedImageUrl.value = '';
        imageError.value = true;
      }
    }
  }
};

// 清理 blob URL（浏览器环境）
onUnmounted(() => {
  if (cachedImageUrl.value && cachedImageUrl.value.startsWith('blob:')) {
    URL.revokeObjectURL(cachedImageUrl.value);
  }
});

onMounted(() => {
  nextTick(() => {
    updateContent();
    // 如果是图片，加载缓存
    if (isImage.value) {
      loadCachedImage();
    }
  });
});

watch(() => props.content, () => {
  nextTick(() => {
    updateContent();
  });
});

// 监听 actualFileUrl 变化，重新加载缓存
watch(() => actualFileUrl.value, () => {
  if (isImage.value) {
    loadCachedImage();
  } else {
    cachedImageUrl.value = '';
  }
});

watch(() => [props.fileUrl, props.fileName, props.content, shouldShowContent.value], () => {
  nextTick(() => {
    updateContent();
  });
});
</script>

<style scoped>
.markdown-message {
  word-wrap: break-word;
  word-break: break-word;
  line-height: 1.6;
  font-size: 14px;
  overflow-wrap: break-word;
  margin: 0;
  padding: 0;
  width: 100%;
  box-sizing: border-box;
}

/* 图片消息时，确保背景色不受消息气泡影响 */
.markdown-message:has(.message-image) {
  background: transparent !important;
}

.message-file {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  background: #F8F8F8;
  border-radius: 8px;
  margin-bottom: 8px;
  transition: all 0.2s ease;
  max-width: 280px;
}

.markdown-message.message-sent .message-file {
  background: #F0F0F0;
}

.message-file:hover {
  background: #F5F5F5;
}

.markdown-message.message-sent .message-file:hover {
  background: #EBEBEB;
}

.file-icon-wrapper {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #FFFFFF;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.file-icon-wrapper :deep(.q-icon) {
  color: #5766F2;
  font-size: 24px;
}

.file-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.file-name-wrapper {
  flex: 1;
  min-width: 0;
}

.file-name {
  color: #191919;
  text-decoration: none;
  font-size: 15px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
  cursor: pointer;
  transition: color 0.2s ease;
}

.file-name:hover {
  color: #5766F2;
  text-decoration: none;
}

.file-name-disabled {
  cursor: default;
  opacity: 0.6;
}

.file-name-disabled:hover {
  color: #191919;
}

.file-size {
  font-size: 12px;
  color: #999999;
  margin-top: 2px;
}

.message-image {
  position: relative;
  display: inline-block;
  max-width: 200px;
  min-width: 100px;
  border-radius: 8px;
  overflow: hidden;
  margin: 0;
  background: #F7F7F7 !important;
  border: none !important;
  outline: none !important;
}

.image-preview {
  max-width: 200px;
  max-height: 200px;
  min-width: 100px;
  min-height: 100px;
  width: auto;
  height: auto;
  display: block;
  border-radius: 8px;
  cursor: pointer;
  transition: opacity 0.2s ease;
  object-fit: contain;
  border: none !important;
  outline: none !important;
  margin: 0 !important;
  padding: 0 !important;
  box-sizing: border-box;
  background: #F7F7F7 !important;
}

.image-preview:hover {
  opacity: 0.85;
}

.image-preview:focus {
  outline: none !important;
  border: none !important;
}

.image-preview:active {
  outline: none !important;
  border: none !important;
}

.image-loading,
.image-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 200px;
  height: 200px;
  background: #F7F7F7 !important;
  border-radius: 8px;
  color: rgba(0, 0, 0, 0.4);
  font-size: 12px;
  gap: 8px;
}

.image-error {
  background: #F7F7F7 !important;
}

.image-caption {
  margin-top: 4px;
  font-size: 12px;
  color: rgba(0, 0, 0, 0.6);
  text-align: center;
  padding: 4px 8px;
}

.markdown-message.message-sent .image-caption {
  color: rgba(0, 0, 0, 0.6);
}

.markdown-content {
  color: inherit;
  margin-top: 8px;
}

.markdown-content :deep(p) {
  margin: 0 0 8px 0;
  color: inherit;
}

.markdown-content :deep(p:last-child) {
  margin-bottom: 0;
}

.markdown-content :deep(code) {
  background: rgba(0, 0, 0, 0.08);
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 0.9em;
  color: inherit;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
}

.markdown-message.message-sent .markdown-content :deep(code) {
  background: rgba(0, 0, 0, 0.06);
}

.markdown-content :deep(pre) {
  background: rgba(0, 0, 0, 0.05);
  padding: 12px;
  border-radius: 6px;
  overflow-x: auto;
  margin: 8px 0;
  border: 1px solid rgba(0, 0, 0, 0.1);
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
  font-size: 13px;
  line-height: 1.5;
}

.markdown-message.message-sent .markdown-content :deep(pre) {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.1);
}

.markdown-content :deep(pre code) {
  background: none;
  padding: 0;
  border: none;
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

.markdown-content :deep(h1),
.markdown-content :deep(h2),
.markdown-content :deep(h3),
.markdown-content :deep(h4),
.markdown-content :deep(h5),
.markdown-content :deep(h6) {
  margin: 12px 0 8px 0;
  color: inherit;
  font-weight: 600;
}

.markdown-content :deep(ul),
.markdown-content :deep(ol) {
  margin: 8px 0;
  padding-left: 24px;
}

.markdown-content :deep(blockquote) {
  border-left: 3px solid rgba(0, 0, 0, 0.1);
  padding-left: 12px;
  margin: 8px 0;
  color: inherit;
  opacity: 0.8;
  font-style: italic;
}

.markdown-message.message-sent .markdown-content :deep(blockquote) {
  border-left-color: rgba(0, 0, 0, 0.1);
}

.markdown-content :deep(table) {
  border-collapse: collapse;
  margin: 8px 0;
  width: 100%;
}

.markdown-content :deep(table th),
.markdown-content :deep(table td) {
  border: 1px solid rgba(0, 0, 0, 0.1);
  padding: 6px 12px;
  text-align: left;
}

.markdown-message.message-sent .markdown-content :deep(table th),
.markdown-message.message-sent .markdown-content :deep(table td) {
  border-color: rgba(0, 0, 0, 0.1);
}

.markdown-content :deep(table th) {
  background: rgba(0, 0, 0, 0.05);
  font-weight: 600;
}

.markdown-message.message-sent .markdown-content :deep(table th) {
  background: rgba(0, 0, 0, 0.05);
}

.markdown-content :deep(hr) {
  border: none;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  margin: 16px 0;
}

.markdown-message.message-sent .markdown-content :deep(hr) {
  border-top-color: rgba(0, 0, 0, 0.1);
}

.markdown-content :deep(img) {
  max-width: 100%;
  height: auto;
  border-radius: 0;
  margin: 8px 0;
  border: none;
}

.markdown-content :deep(a) {
  color: inherit;
  text-decoration: underline;
  opacity: 0.9;
}

.markdown-content :deep(a:hover) {
  opacity: 1;
}

.markdown-content :deep(strong) {
  font-weight: 600;
}

.markdown-content :deep(em) {
  font-style: italic;
}

.markdown-content :deep(del) {
  text-decoration: line-through;
}

.markdown-content :deep(.mention) {
  color: #5766F2;
  font-weight: 500;
  background: rgba(87, 102, 242, 0.08);
  padding: 3px 8px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  display: inline-block;
  line-height: 1.4;
  font-size: inherit;
  box-shadow: 0 1px 2px rgba(87, 102, 242, 0.05);
}

.markdown-content :deep(.mention:hover) {
  background: rgba(87, 102, 242, 0.12);
  color: #4C5FD5;
  box-shadow: 0 2px 4px rgba(87, 102, 242, 0.08);
  transform: translateY(-1px);
}

.markdown-content :deep(.mention:active) {
  background: rgba(87, 102, 242, 0.15);
  transform: translateY(0);
}

/* 被@的当前用户高亮样式（更醒目的颜色） */
.markdown-content :deep(.mention.mention-self) {
  color: #FF6B6B;
  background: rgba(255, 107, 107, 0.15);
  font-weight: 600;
  box-shadow: 0 1px 3px rgba(255, 107, 107, 0.2);
}

.markdown-content :deep(.mention.mention-self:hover) {
  background: rgba(255, 107, 107, 0.25);
  color: #FF5252;
  box-shadow: 0 2px 6px rgba(255, 107, 107, 0.3);
  transform: translateY(-1px);
}

.markdown-content :deep(.mention.mention-self:active) {
  background: rgba(255, 107, 107, 0.3);
  transform: translateY(0);
}
</style>
